How to create forms with Server Actions
React Server Actions are Server Functions that execute on the server. They can be called in Server and Client Components to handle form submissions. This guide will walk you through how to create forms in Next.js with Server Actions.
How it works
React extends the HTML <form>
element to allow Server Actions to be invoked with the action
attribute.
When used in a form, the function automatically receives the FormData
object. You can then extract the data using the native FormData
methods:
export default function Page() {
async function createInvoice(formData: FormData) {
'use server'
const rawFormData = {
customerId: formData.get('customerId'),
amount: formData.get('amount'),
status: formData.get('status'),
}
// mutate data
// revalidate the cache
}
return <form action={createInvoice}>...</form>
}
Good to know: When working with forms that have multiple fields, you can use the
entries()
method with JavaScript'sObject.fromEntries()
. For example:const rawFormData = Object.fromEntries(formData)
.
Passing additional arguments
Outside of form fields, you can pass additional arguments to a Server Function using the JavaScript bind
method. For example, to pass the userId
argument to the updateUser
Server Function:
'use client'
import { updateUser } from './actions'
export function UserProfile({ userId }: { userId: string }) {
const updateUserWithId = updateUser.bind(null, userId)
return (
<form action={updateUserWithId}>
<input type="text" name="name" />
<button type="submit">Update User Name</button>
</form>
)
}
The Server Function will receive the userId
as an additional argument:
'use server'
export async function updateUser(userId: string, formData: FormData) {}
Good to know:
- An alternative is to pass arguments as hidden input fields in the form (e.g.
<input type="hidden" name="userId" value={userId} />
). However, the value will be part of the rendered HTML and will not be encoded.bind
works in both Server and Client Components and supports progressive enhancement.
Form validation
Forms can be validate on the client or server.
- For client-side validation, you can use the HTML attributes like
required
andtype="email"
for basic validation. - For server-side validation, you can use a library like zod to validate the form fields. For example:
'use server'
import { z } from 'zod'
const schema = z.object({
email: z.string({
invalid_type_error: 'Invalid Email',
}),
})
export default async function createUser(formData: FormData) {
const validatedFields = schema.safeParse({
email: formData.get('email'),
})
// Return early if the form data is invalid
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
}
}
// Mutate data
}
Validation errors
To display validation errors or messages, turn the component that defines the <form>
into a Client Component and use React useActionState
.
When using useActionState
, the Server function signature will change to receive a new prevState
or initialState
parameter as its first argument.
'use server'
import { z } from 'zod'
export async function createUser(initialState: any, formData: FormData) {
const validatedFields = schema.safeParse({
email: formData.get('email'),
})
// ...
}
You can then conditionally render the error message based on the state
object.
'use client'
import { useActionState } from 'react'
import { createUser } from '@/app/actions'
const initialState = {
message: '',
}
export function Signup() {
const [state, formAction, pending] = useActionState(createUser, initialState)
return (
<form action={formAction}>
<label htmlFor="email">Email</label>
<input type="text" id="email" name="email" required />
{/* ... */}
<p aria-live="polite">{state?.message}</p>
<button disabled={pending}>Sign up</button>
</form>
)
}
Pending states
The useActionState
hook exposes a pending
boolean that can be used to show a loading indicator or disable the submit button while the action is being executed.
'use client'
import { useActionState } from 'react'
import { createUser } from '@/app/actions'
export function Signup() {
const [state, formAction, pending] = useActionState(createUser, initialState)
return (
<form action={formAction}>
{/* Other form elements */}
<button disabled={pending}>Sign up</button>
</form>
)
}
Alternatively, you can use the useFormStatus
hook to show a loading indicator while the action is being executed. When using this hook, you'll need to create a separate component to render the loading indicator. For example, to disable the button when the action is pending:
'use client'
import { useFormStatus } from 'react-dom'
export function SubmitButton() {
const { pending } = useFormStatus()
return (
<button disabled={pending} type="submit">
Sign Up
</button>
)
}
You can then nest the SubmitButton
component inside the form:
import { SubmitButton } from './button'
import { createUser } from '@/app/actions'
export function Signup() {
return (
<form action={createUser}>
{/* Other form elements */}
<SubmitButton />
</form>
)
}
Good to know: In React 19,
useFormStatus
includes additional keys on the returned object, like data, method, and action. If you are not using React 19, only thepending
key is available.
Optimistic updates
You can use the React useOptimistic
hook to optimistically update the UI before the Server Function finishes executing, rather than waiting for the response:
'use client'
import { useOptimistic } from 'react'
import { send } from './actions'
type Message = {
message: string
}
export function Thread({ messages }: { messages: Message[] }) {
const [optimisticMessages, addOptimisticMessage] = useOptimistic<
Message[],
string
>(messages, (state, newMessage) => [...state, { message: newMessage }])
const formAction = async (formData: FormData) => {
const message = formData.get('message') as string
addOptimisticMessage(message)
await send(message)
}
return (
<div>
{optimisticMessages.map((m, i) => (
<div key={i}>{m.message}</div>
))}
<form action={formAction}>
<input type="text" name="message" />
<button type="submit">Send</button>
</form>
</div>
)
}
Nested form elements
You can call Server Actions in elements nested inside <form>
such as <button>
, <input type="submit">
, and <input type="image">
. These elements accept the formAction
prop or event handlers.
This is useful in cases where you want to call multiple Server Actions within a form. For example, you can create a specific <button>
element for saving a post draft in addition to publishing it. See the React <form>
docs for more information.
Programmatic form submission
You can trigger a form submission programmatically using the requestSubmit()
method. For example, when the user submits a form using the ⌘
+ Enter
keyboard shortcut, you can listen for the onKeyDown
event:
'use client'
export function Entry() {
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (
(e.ctrlKey || e.metaKey) &&
(e.key === 'Enter' || e.key === 'NumpadEnter')
) {
e.preventDefault()
e.currentTarget.form?.requestSubmit()
}
}
return (
<div>
<textarea name="entry" rows={20} required onKeyDown={handleKeyDown} />
</div>
)
}
This will trigger the submission of the nearest <form>
ancestor, which will invoke the Server Function.
Was this helpful?